using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TheBoard.Models.YepNope
{
    public class YepNopeConfig : IYepNope
    {
        public YepNopeConfig() : this("", new Dictionary<string, YepNopeConfig>(StringComparer.OrdinalIgnoreCase))
        {
            Groups[""] = this;
        }

        protected YepNopeConfig(string groupName, Dictionary<string, YepNopeConfig> groups)
        {
            GroupName = groupName;
            Groups = groups;
            YepUrls = new List<string>();
            NopeUrls = new List<string>();
            LoadUrls = new List<string>();
            BothUrls = new List<string>();
            CompleteScripts = new List<string>();
            DependsOn = "";
            Test = "true";
        }

        protected string GroupName { get; set; }
        protected Dictionary<string, YepNopeConfig> Groups { get; set; }
        protected List<string> YepUrls { get; set; }
        protected List<string> NopeUrls { get; set; }
        protected List<string> LoadUrls { get; set; }
        protected List<string> BothUrls { get; set; }
        protected List<string> CompleteScripts { get; set; }
        protected string DependsOn { get; set; }

        #region IYepNope Members

        public string Test { get; set; }

        public IYepNope Yep(params string[] urls)
        {
            YepUrls.AddRange(urls);
            return this;
        }

        public IYepNope Nope(params string[] urls)
        {
            NopeUrls.AddRange(urls);
            return this;
        }

        public IYepNope Load(params string[] urls)
        {
            LoadUrls.AddRange(urls);
            return this;
        }

        public IYepNope Both(params string[] urls)
        {
            BothUrls.AddRange(urls);
            return this;
        }

        public IYepNope Complete(string script)
        {
            CompleteScripts.Add(script);
            return this;
        }

        public IYepNope DependsOnGroup(string groupName)
        {
            DependsOn = groupName;
            return this;
        }

        public IYepNope Group(string groupName)
        {
            YepNopeConfig group;
            if (!Groups.TryGetValue(groupName, out group))
            {
                group = new YepNopeConfig(groupName, Groups);
                Groups.Add(groupName, group);
            }
            return group;
        }

        #endregion


        public string Write(string yepnope)
        {
            var dependsOnLookup = Groups.Values.ToLookup(g => g.DependsOn, g => g);

            var w = new Writer(yepnope, dependsOnLookup);
            WriteYepNope(w);
            return w.ToString();
        }

        private void WriteYepNope(Writer w)
        {
            w.BeginYepNope();
            var i = 0;
            foreach (var group in w.DependsOn[GroupName].Where(g => w.Register(g.GroupName)).Where(g => g.HasAnythingToLoad))
            {
                if (0 < i++)
                {
                    w.Write(",");
                }
                group.Write(w);
            }
            w.EndYepNope();
        }

        protected bool HasAnythingToLoad
        {
            get { return (YepUrls.Count > 0) || (NopeUrls.Count > 0) || (LoadUrls.Count > 0) || (BothUrls.Count > 0); }
        }

        private void Write(Writer w)
        {
            w.BeginObject();
            w.NewLine("test: ", Test, ",");
            w.NewLine("yep: ", GetUrlArrayText(YepUrls), ",");
            w.NewLine("nope: ", GetUrlArrayText(NopeUrls), ",");
            w.NewLine("load: ", GetUrlArrayText(LoadUrls), ",");
            w.NewLine("both: ", GetUrlArrayText(BothUrls), ",");
            w.NewLine("complete: function ()");
            w.BeginObject();
            WriteYepNope(w);
            foreach (var completeScript in CompleteScripts)
            {
                w.NewLine(completeScript);
            }
            w.EndObject();

            w.EndObject();
        }

        private string GetUrlArrayText(IEnumerable<string> urls)
        {
            return "[" + string.Join(",", urls.Select(s => "\"" + s + "\"")) + "]";

        }

        private class Writer
        {
            public ILookup<string, YepNopeConfig> DependsOn { get; private set; }
            private readonly string _yepnope;
            private readonly StringBuilder _sb = new StringBuilder();
            private int _indent;
            private HashSet<string> _visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            public Writer(string yepnope, ILookup<string, YepNopeConfig> dependsOnLookup)
            {
                DependsOn = dependsOnLookup;
                _yepnope = yepnope;
            }

            public void BeginYepNope()
            {
                NewLine(_yepnope, "([");
                ++_indent;
            }

            public void EndYepNope()
            {
                --_indent;
                NewLine("]);");
            }

            private void AppendIndent()
            {
                _sb.Append(new string('\t', _indent));
            }


            public void BeginObject()
            {
                NewLine("{");
                ++_indent;
                
            }

            public void EndObject()
            {
                --_indent;
                NewLine("}");
            }

            public void Write(params string[] strings)
            {
                foreach (var s in strings)
                {
                    _sb.Append(s);
                }
            }

            public void NewLine(params string[] strings)
            {
                _sb.AppendLine();
                AppendIndent();
                foreach(var s in strings)
                {
                    _sb.Append(s);
                }
                
            }

            public override string ToString()
            {
                return _sb.ToString();
            }

            public bool Register(string groupName)
            {
                return _visited.Add(groupName);
            }
        }
    }
}